// Package cmd is an interface for parsing the command line
package cmd

import (
	"fmt"
	"os"
	"strings"

	"gitee.com/zawei/yggdrasil/pkg"
	"gitee.com/zawei/yggdrasil/third_party/urfave/cli"
)

type Cmd interface {
	// The cli app within this cmd
	App() *cli.App
	// Adds options
	WithOptions(opts ...Option)
	// Run command
	Run() error
	// Options set within this command
	Options() Options
}

type cmd struct {
	opts Options
	app  *cli.App
}

var (
	defaultCmd = newCmd(
		Flags(
			&cli.StringFlag{
				Name:    "config_path",
				Aliases: []string{"conf", "c"},
				EnvVars: []string{fmt.Sprintf("%s_%s", strings.ToUpper(pkg.AppName()), "CONFIG_PATH")},
				Value:   "./conf",
				Usage:   "The source of the config to be used to get configuration",
			},
		),
	)
)

func newCmd(opts ...Option) Cmd {
	options := Options{
		Description: fmt.Sprintf("Use `%s [command] --help` to see command specific help.", pkg.AppName()),
		Flags:       make([]cli.Flag, 0),
	}

	for _, o := range opts {
		o(&options)
	}

	cmd := new(cmd)
	cmd.opts = options
	cmd.app = cli.NewApp()
	cmd.app.Version = pkg.AppVersion()
	cmd.app.Name = cmd.opts.Name
	cmd.app.Version = cmd.opts.Version
	cmd.app.Description = cmd.opts.Description
	cmd.app.Usage = "An app based on yggdrasil"
	cmd.app.Before = cmd.Before
	cmd.app.Flags = options.Flags
	cmd.app.Commands = options.Commands
	cmd.app.HideHelp = false
	cmd.app.HideHelpCommand = false

	cmd.app.Action = func(c *cli.Context) error {
		return nil
	}
	cmd.app.OnUsageError = func(ctx *cli.Context, err error, _ bool) error {
		_, _ = fmt.Fprintf(cmd.app.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
		_ = cli.ShowAppHelp(ctx)
		return nil
	}
	if len(options.Version) == 0 {
		cmd.app.HideVersion = true
	}

	return cmd
}

func (c *cmd) App() *cli.App {
	return c.app
}

func (c *cmd) Options() Options {
	return c.opts
}

func (c *cmd) Before(ctx *cli.Context) error {
	return nil
}

func (c *cmd) WithOptions(opts ...Option) {
	for _, o := range opts {
		o(&c.opts)
	}
}

func (c *cmd) Run() error {
	for i := 0; i < len(c.opts.Commands); i++ {
		existed := false
		for _, cmd := range c.app.Commands {
			if cmd.Name == c.opts.Commands[i].Name {
				existed = true
				break
			}
		}
		if !existed {
			c.app.Commands = append(c.app.Commands, c.opts.Commands[i])
		}
	}
	for i := 0; i < len(c.opts.Flags); i++ {
		existed := false
		for _, flag := range c.app.Flags {
			if flag.Names()[0] == c.opts.Flags[i].Names()[0] {
				existed = true
				break
			}
		}
		if !existed {
			c.app.Flags = append(c.app.Flags, c.opts.Flags[i])
		}
	}
	c.app.HideVersion = len(c.opts.Version) == 0
	c.app.Usage = c.opts.Description
	return c.app.Run(os.Args)
}

func DefaultOptions() Options {
	return defaultCmd.Options()
}

func App() *cli.App {
	return defaultCmd.App()
}

func WithOptions(opts ...Option) {
	defaultCmd.WithOptions(opts...)
}

func GetOptions() Options {
	return defaultCmd.Options()
}

func Run() error {
	if defaultCmd.Options().Action != nil {
		defaultCmd.App().Action = defaultCmd.Options().Action
	}
	return defaultCmd.Run()
}
